//
// Created by yuan on 2/26/21.
//

#ifndef OTL_TIMER_H
#define OTL_TIMER_H

#include <iostream>
#include <memory>
#include <functional>
#include <chrono>
#include <vector>
#include <cstdint>
#include <algorithm>
#include <unordered_map>

namespace enrigin {

    uint64_t getTimeSec();
    uint64_t getTimeMsec();
    uint64_t getTimeUsec();
    void msleep(int msec);
    void usleep(int usec);
    std::string timeToString(time_t seconds);

    class TimerQueue {
    public:
        static std::shared_ptr<TimerQueue> create();
        virtual ~TimerQueue() {
            std::cout << "TimerQueue dtor" << std::endl;
        };

        virtual int createTimer(uint32_t delayMsec, uint32_t skew, std::function<void()> func, int repeat, uint64_t *pTimerId) = 0;
        virtual int deleteTimer(uint64_t timerId) = 0;
        virtual size_t count() = 0;
        virtual int runLoop() = 0;
        virtual int stop() = 0;
    };

    using TimerQueuePtr = std::shared_ptr<TimerQueue>;

    class StatTool {
    public:
        static std::shared_ptr<StatTool> create(int range=5);
        virtual ~StatTool(){};

        virtual void update(uint64_t currentStatis=1) = 0;
        virtual void reset() = 0;
        virtual double getkbps() = 0;
        virtual double getSpeed() = 0;
    };

    using StatToolPtr = std::shared_ptr<StatTool>;

    class Perf {
        // timing state
        std::chrono::steady_clock::time_point start_tp_{};
        std::string current_tag_;

        // default threshold and window applied when a new tag is first seen
        int default_threshold_ms_{50};
        int default_window_{5};

        struct Stats {
            int window{5};
            std::vector<int64_t> hist_us; // ring buffer of microseconds
            size_t idx{0};
            size_t filled{0};
            int64_t sum_us{0};
            int threshold_ms{0};
        };

        std::unordered_map<std::string, Stats> stats_;

        static bool &enabledFlag() {
            static bool enabled = true; // default: enabled
            return enabled;
        }

        // Thread-local storage for per-thread start timepoints per Perf instance and tag.
        using TimePoint = std::chrono::steady_clock::time_point;
        static auto &startsMap() {
            thread_local std::unordered_map<const Perf*, std::unordered_map<std::string, TimePoint>> m;
            return m;
        }

        Stats &ensureStats(const std::string &tag) {
            auto &s = stats_[tag];
            if (s.hist_us.empty()) {
                s.window = default_window_ > 0 ? default_window_ : 1;
                s.hist_us.assign(static_cast<size_t>(s.window), 0);
                s.idx = 0; s.filled = 0; s.sum_us = 0;
                s.threshold_ms = default_threshold_ms_;
            }
            return s;
        }

        void recordSample(const std::string &tag, int64_t delta_us) {
            auto &s = ensureStats(tag);
            if (s.window <= 0) return;
            if (s.filled < static_cast<size_t>(s.window)) {
                s.hist_us[s.idx] = delta_us;
                s.sum_us += delta_us;
                ++s.filled;
                s.idx = (s.idx + 1) % static_cast<size_t>(s.window);
            } else {
                s.sum_us -= s.hist_us[s.idx];
                s.hist_us[s.idx] = delta_us;
                s.sum_us += delta_us;
                s.idx = (s.idx + 1) % static_cast<size_t>(s.window);
            }
        }

    public:
        Perf() = default;
        // Backward-compatible constructor
        Perf(const std::string &name, int threshold = 0) { start(name, threshold); }
        // New constructor allowing window size
        Perf(const std::string &name, int threshold, int window) {
            setWindow(window);
            start(name, threshold);
        }
        ~Perf() = default;

        // Global enable/disable
        static void setEnabled(bool enabled) { enabledFlag() = enabled; }
        static bool isEnabled() { return enabledFlag(); }

        // Configure default window size (number of recent samples used for average) for all tags
        void setWindow(int window) {
            if (window <= 0) window = 1;
            default_window_ = window;
            // reset all existing tag windows and clear their history
            for (auto &kv : stats_) {
                auto &s = kv.second;
                s.window = default_window_;
                s.hist_us.assign(static_cast<size_t>(s.window), 0);
                s.idx = 0; s.filled = 0; s.sum_us = 0;
            }
        }
        int window() const { return default_window_; }
        // Set window for a specific tag (clears that tag's history)
        void setWindow(const std::string &tag, int window) {
            if (window <= 0) window = 1;
            auto &s = ensureStats(tag);
            s.window = window;
            s.hist_us.assign(static_cast<size_t>(s.window), 0);
            s.idx = 0; s.filled = 0; s.sum_us = 0;
        }

        // Start/Stop API
        void start(const std::string &name, int threshold = 0) {
            if (!isEnabled()) return;
            current_tag_ = name;
            auto &s = ensureStats(current_tag_);
            // Update this tag's threshold if provided
            s.threshold_ms = threshold;
            auto now = std::chrono::steady_clock::now();
            start_tp_ = now; // keep for backward compatibility with stop()
            startsMap()[this][current_tag_] = now; // per-thread start time for this tag
        }
        void stop() {
            if (!isEnabled()) return;
            if (!current_tag_.empty()) { stop(current_tag_); }
        }

        // New: stop for a specific tag, to pair with start(tag) and avoid cross-thread interference.
        void stop(const std::string &tag) {
            if (!isEnabled()) return;
            auto now = std::chrono::steady_clock::now();
            auto &perfs = startsMap();
            auto itPerf = perfs.find(this);
            if (itPerf == perfs.end()) return; // no start recorded in this thread
            auto &byTag = itPerf->second;
            auto it = byTag.find(tag);
            if (it == byTag.end()) return; // start(tag) not called in this thread
            auto start_tp = it->second;
            byTag.erase(it);

            auto delta_us = std::chrono::duration_cast<std::chrono::microseconds>(now - start_tp).count();
            recordSample(tag, delta_us);
        }

        // Stop and immediately print the elapsed time for the current tag
        void stopAndPrint() {
            if (!isEnabled()) return;
            if (current_tag_.empty()) return;
            stopAndPrint(current_tag_);
        }

        // Stop and immediately print the elapsed time for a specific tag
        void stopAndPrint(const std::string &tag) {
            if (!isEnabled()) return;
            auto now = std::chrono::steady_clock::now();
            auto &perfs = startsMap();
            auto itPerf = perfs.find(this);
            if (itPerf == perfs.end()) return;
            auto &byTag = itPerf->second;
            auto it = byTag.find(tag);
            if (it == byTag.end()) return;
            auto start_tp = it->second;
            byTag.erase(it);

            auto delta_us = std::chrono::duration_cast<std::chrono::microseconds>(now - start_tp).count();
            recordSample(tag, delta_us);
            double ms = static_cast<double>(delta_us) / 1000.0;
            if (ms > stats_[tag].threshold_ms) {
                std::cout << "[PERF] " << tag << ": " << ms << " ms" << std::endl;
            }

        }

        // Average getters (for current tag)
        double averageUs() const {
            if (current_tag_.empty()) return 0.0;
            auto it = stats_.find(current_tag_);
            if (it == stats_.end() || it->second.filled == 0) return 0.0;
            return static_cast<double>(it->second.sum_us) / static_cast<double>(it->second.filled);
        }
        double averageMs() const { return averageUs() / 1000.0; }
        size_t sampleCount() const {
            if (current_tag_.empty()) return 0;
            auto it = stats_.find(current_tag_);
            if (it == stats_.end()) return 0;
            return it->second.filled;
        }
        void resetAverage() {
            if (current_tag_.empty()) return;
            auto &s = ensureStats(current_tag_);
            std::fill(s.hist_us.begin(), s.hist_us.end(), 0);
            s.idx = 0; s.filled = 0; s.sum_us = 0;
        }

        // Average getters (for a specific tag)
        double averageUs(const std::string &tag) const {
            auto it = stats_.find(tag);
            if (it == stats_.end() || it->second.filled == 0) return 0.0;
            return static_cast<double>(it->second.sum_us) / static_cast<double>(it->second.filled);
        }
        double averageMs(const std::string &tag) const { return averageUs(tag) / 1000.0; }
        size_t sampleCount(const std::string &tag) const {
            auto it = stats_.find(tag);
            if (it == stats_.end()) return 0;
            return it->second.filled;
        }
        void resetAverage(const std::string &tag) {
            auto &s = ensureStats(tag);
            std::fill(s.hist_us.begin(), s.hist_us.end(), 0);
            s.idx = 0; s.filled = 0; s.sum_us = 0;
        }
    };
} // namespace enrigin

#if !defined(ENRIGIN_NO_COMPAT_ALIAS)
#ifndef ENRIGIN_ALIAS_DEFINED
#define ENRIGIN_ALIAS_DEFINED
namespace otl = enrigin;
#endif
#endif

#endif // OTL_TIMER_H
